"""Seed utilities for Exploration Mode (nodes, quests, achievements).

Call `seed_exploration_if_empty(db)` on startup or via a management command.

Now uses idempotent ensure-* helpers so that new content can be added on
subsequent runs without wiping the database.
"""

from __future__ import annotations
from sqlalchemy.orm import Session
from app.models.exploration_core import (
    ExplorationNode,
    Quest,
    Achievement,
    NodeEdge,
    UserQuest,
)


def seed_exploration_if_empty(db: Session) -> dict:
    created_nodes = 0
    created_quests = 0
    created_achs = 0
    removed_legacy_reads = 0

    def ensure_node(code: str, **defaults) -> ExplorationNode:
        nonlocal created_nodes
        n = db.query(ExplorationNode).filter(ExplorationNode.code == code).first()
        if n:
            # 轻度同步可选：若级别/分类/描述发生变化可保留现值或按需更新
            return n
        n = ExplorationNode(code=code, **defaults)
        db.add(n)
        db.commit()
        db.refresh(n)
        created_nodes += 1
        return n

    def ensure_quest_read(
        node: ExplorationNode,
        title: str,
        *,
        min_seconds: int,
        reward: int,
        order_index: int,
        desc: str = "",
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        q = Quest(
            node_id=node.id,
            title=title,
            type="READ",
            description=desc,
            requirement_json={"min_read_seconds": int(min_seconds)},
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_quest_visit(
        node: ExplorationNode,
        title: str,
        *,
        target: str,
        reward: int,
        order_index: int,
        desc: str = "",
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        q = Quest(
            node_id=node.id,
            title=title,
            type="VISIT",
            description=desc,
            requirement_json={"target": target},
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_quest_collect(
        node: ExplorationNode,
        title: str,
        *,
        count: int,
        reward: int,
        order_index: int,
        desc: str = "",
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        q = Quest(
            node_id=node.id,
            title=title,
            type="COLLECT",
            description=desc,
            requirement_json={"count": int(count)},
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_quest_quiz_select(
        node: ExplorationNode,
        title: str,
        *,
        question: str,
        mode: str,  # "single" | "multi"
        options: list[dict],
        answer: list[str],
        reward: int,
        order_index: int,
        desc: str = "",
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        q = Quest(
            node_id=node.id,
            title=title,
            type="QUIZ_SELECT",
            description=desc,
            requirement_json={
                "question": question,
                "mode": mode,
                "options": options,
                "answer": answer,
            },
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_quest_match(
        node: ExplorationNode,
        title: str,
        *,
        items: list[dict],  # [{left: str, right: str}, ...]
        pairs: int | None,
        reward: int,
        order_index: int,
        desc: str = "",
        time_limit: int | None = None,
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        from typing import Any

        req: dict[str, Any] = {"items": items}
        if pairs is not None:
            req["pairs"] = int(pairs)
        if time_limit:
            req["time_limit"] = int(time_limit)
        q = Quest(
            node_id=node.id,
            title=title,
            type="MATCH",
            description=desc,
            requirement_json=req,
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_quest_order(
        node: ExplorationNode,
        title: str,
        *,
        sequence: list[str],
        initial: list[str] | None,
        reward: int,
        order_index: int,
        desc: str = "",
        time_limit: int | None = None,
    ) -> None:
        nonlocal created_quests
        q = (
            db.query(Quest)
            .filter(Quest.node_id == node.id, Quest.title == title)
            .first()
        )
        if q:
            return
        from typing import Any

        req: dict[str, Any] = {"sequence": list(sequence)}
        if initial:
            req["initial"] = list(initial)
        if time_limit:
            req["time_limit"] = int(time_limit)
        q = Quest(
            node_id=node.id,
            title=title,
            type="ORDER",
            description=desc,
            requirement_json=req,
            reward_exp=int(reward),
            order_index=order_index,
        )
        db.add(q)
        db.commit()
        created_quests += 1

    def ensure_edge(from_code: str, to_code: str) -> None:
        f = db.query(ExplorationNode).filter(ExplorationNode.code == from_code).first()
        t = db.query(ExplorationNode).filter(ExplorationNode.code == to_code).first()
        if not f or not t:
            return
        exists = (
            db.query(NodeEdge)
            .filter(
                NodeEdge.from_node_id == f.id,
                NodeEdge.to_node_id == t.id,
                NodeEdge.relation_type == "unlock",
            )
            .first()
        )
        if exists:
            return
        db.add(
            NodeEdge(
                from_node_id=f.id, to_node_id=t.id, relation_type="unlock", weight=1
            )
        )
        db.commit()

    def ensure_achievement(
        code: str, title: str, desc: str, cond: dict, points: int
    ) -> None:
        nonlocal created_achs
        a = db.query(Achievement).filter(Achievement.code == code).first()
        if a:
            return
        a = Achievement(
            code=code,
            title=title,
            description=desc,
            condition_json=cond,
            reward_points=points,
        )
        db.add(a)
        db.commit()
        created_achs += 1

    # --- Nodes ---
    n_ddj = ensure_node(
        "root_daodejing",
        title="道德经 · 入门",
        short_desc="老子之道，一以贯之。",
        level=1,
        category="哲学",
        order_index=1,
        is_unlocked_default=True,
    )
    n_sj = ensure_node(
        "root_shijing",
        title="诗经 · 风雅颂",
        short_desc="中国最早的诗歌总集。",
        level=1,
        category="诗歌",
        order_index=2,
        is_unlocked_default=True,
    )
    n_shj = ensure_node(
        "root_shanhaijing",
        title="山海经 · 异志",
        short_desc="上古地理与神话志怪。",
        level=2,
        category="志怪",
        order_index=3,
        is_unlocked_default=False,
    )
    n_szb = ensure_node(
        "root_sunzibingfa",
        title="孙子兵法 · 用兵",
        short_desc="兵者，国之大事。",
        level=2,
        category="兵法",
        order_index=4,
        is_unlocked_default=False,
    )
    n_tgkw = ensure_node(
        "root_tiangongkaiwu",
        title="天工开物 · 造物",
        short_desc="明代工艺百科全书，论物之理，述器之用。",
        level=3,
        category="工艺",
        order_index=5,
        is_unlocked_default=False,
    )
    n_zz = ensure_node(
        "root_zhuangzi",
        title="庄子 · 逍遥",
        short_desc="齐物逍遥，寓言哲思。",
        level=3,
        category="哲学",
        order_index=6,
        is_unlocked_default=False,
    )
    n_zy = ensure_node(
        "root_zhouyi",
        title="周易 · 卦象",
        short_desc="群经之首，变化之道。",
        level=4,
        category="易学",
        order_index=7,
        is_unlocked_default=False,
    )
    n_zz_lv5 = ensure_node(
        "root_zuozhuan",
        title="左传 · 春秋",
        short_desc="编年史之典范，春秋大义详解。",
        level=5,
        category="史学",
        order_index=8,
        is_unlocked_default=False,
    )
    n_xz_lv5 = ensure_node(
        "root_xunzi",
        title="荀子 · 性恶论",
        short_desc="礼法并重，性恶而教可化。",
        level=5,
        category="哲学",
        order_index=9,
        is_unlocked_default=False,
    )

    # --- Intangible Cultural Heritage (非遗) Nodes ---
    # Lv1：皮影戏
    n_fy_pyx = ensure_node(
        "fy_piyingxi",
        title="非遗 · 皮影戏",
        short_desc="光与影的民间戏曲艺术。",
        level=1,
        category="非遗",
        order_index=11,
        is_unlocked_default=True,
    )
    # Lv2：木版年画
    n_fy_mbnh = ensure_node(
        "fy_mubannianhua",
        title="非遗 · 木版年画",
        short_desc="年节民俗的视觉符号。",
        level=2,
        category="非遗",
        order_index=12,
        is_unlocked_default=False,
    )
    # Lv3：景泰蓝（掐丝珐琅）
    n_fy_jtl = ensure_node(
        "fy_jingtailan",
        title="非遗 · 景泰蓝",
        short_desc="金丝游龙，色釉如海。",
        level=3,
        category="非遗",
        order_index=13,
        is_unlocked_default=False,
    )
    # Lv4：昆曲
    n_fy_kq = ensure_node(
        "fy_kunqu",
        title="非遗 · 昆曲",
        short_desc="百戏之祖，水磨调的千年回响。",
        level=4,
        category="非遗",
        order_index=14,
        is_unlocked_default=False,
    )
    n_fy_xz_lv5 = ensure_node(
        "fy_xuanzhi",
        title="非遗 · 宣纸制作",
        short_desc="千年纸寿，墨韵承载。",
        level=5,
        category="非遗",
        order_index=15,
        is_unlocked_default=False,
    )
    n_fy_gq_lv5 = ensure_node(
        "fy_guqin",
        title="非遗 · 古琴艺术",
        short_desc="琴者，心也。七弦清音，知音难觅。",
        level=5,
        category="非遗",
        order_index=16,
        is_unlocked_default=False,
    )

    # 注意：不再创建“探索小游戏 · 练习场”，小游戏任务下沉到既有节点

    # --- Quests (one READ + examples of VISIT/COLLECT per node) ---
    # Lv1 道德经：1x READ + 1x VISIT + 1x QUIZ_SELECT
    ensure_quest_read(
        n_ddj,
        "片章精读",
        min_seconds=10,
        reward=50,
        order_index=1,
        desc="专注阅读至少 10 秒",
    )
    ensure_quest_visit(
        n_ddj,
        "卷轴已阅",
        target="lore",
        reward=15,
        order_index=2,
        desc="确认已阅读本节点解读",
    )
    ensure_quest_quiz_select(
        n_ddj,
        "谁是《道德经》的作者？",
        question="《道德经》的作者普遍认为是？",
        mode="single",
        options=[
            {"id": "A", "text": "老子"},
            {"id": "B", "text": "孔子"},
            {"id": "C", "text": "庄子"},
        ],
        answer=["A"],
        reward=20,
        order_index=3,
        desc="完成一次节点理解小测验",
    )

    # Lv1 诗经：1x READ + 1x COLLECT
    ensure_quest_read(
        n_sj,
        "吟咏其章",
        min_seconds=12,
        reward=40,
        order_index=1,
        desc="静心阅读至少 12 秒",
    )
    ensure_quest_collect(
        n_sj, "采章集义", count=3, reward=30, order_index=2, desc="按步采撷三处意象要点"
    )
    ensure_quest_quiz_select(
        n_sj,
        "《诗经》常见主题",
        question="以下哪些主题是《诗经》中常见的？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "风土人情"},
            {"id": "B", "text": "政治讽喻"},
            {"id": "C", "text": "神怪志异"},
            {"id": "D", "text": "婚恋歌谣"},
        ],
        answer=["A", "B", "D"],
        reward=20,
        order_index=3,
        desc="多选题：识别主题类型",
    )

    # --- 非遗 Lv1：皮影戏（READ + VISIT + COLLECT + QUIZ_MULTI）---
    ensure_quest_read(
        n_fy_pyx,
        "影戏通史速览",
        min_seconds=10,
        reward=40,
        order_index=1,
        desc="快速了解皮影戏的历史脉络与地域流派",
    )
    ensure_quest_visit(
        n_fy_pyx,
        "观摩光影",
        target="lore",
        reward=15,
        order_index=2,
        desc="确认阅读 AI 卷轴或观看图志片段",
    )
    ensure_quest_collect(
        n_fy_pyx,
        "器具点名",
        count=2,
        reward=20,
        order_index=3,
        desc="记录两种常用器具（灯、影窗、皮稿等）",
    )
    ensure_quest_quiz_select(
        n_fy_pyx,
        "皮影戏关键要素",
        question="下列哪些属于皮影戏表演的关键要素？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "皮影人物"},
            {"id": "B", "text": "光源与影窗"},
            {"id": "C", "text": "瓷胎绘彩"},
            {"id": "D", "text": "说唱与配乐"},
        ],
        answer=["A", "B", "D"],
        reward=25,
        order_index=4,
        desc="理解影、光、唱、演的协同",
    )

    # Lv2 山海经：1x READ + 1x VISIT
    ensure_quest_read(
        n_shj,
        "异想奇游",
        min_seconds=15,
        reward=60,
        order_index=1,
        desc="沉浸阅读至少 15 秒",
    )
    ensure_quest_visit(
        n_shj,
        "图志已览",
        target="atlas",
        reward=20,
        order_index=2,
        desc="确认浏览过相关图志/资料",
    )
    ensure_quest_collect(
        n_shj,
        "异名采集",
        count=2,
        reward=25,
        order_index=3,
        desc="记录两处不同称谓或异名",
    )

    # --- 非遗 Lv2：木版年画（READ + COLLECT + QUIZ_SINGLE）---
    ensure_quest_read(
        n_fy_mbnh,
        "年画渊源",
        min_seconds=12,
        reward=45,
        order_index=1,
        desc="认识木版年画的历史与年节文化",
    )
    ensure_quest_collect(
        n_fy_mbnh,
        "工序清单",
        count=3,
        reward=30,
        order_index=2,
        desc="整理三步工序（刻板/套印/上色等）",
    )
    ensure_quest_quiz_select(
        n_fy_mbnh,
        "年画与节俗",
        question="木版年画与下列哪一项联系最紧密？",
        mode="single",
        options=[
            {"id": "A", "text": "科举殿试"},
            {"id": "B", "text": "岁时年节"},
            {"id": "C", "text": "行军布阵"},
        ],
        answer=["B"],
        reward=20,
        order_index=3,
        desc="年节语境下的象征意味",
    )

    # Lv2 孙子兵法：1x READ + 1x VISIT + 1x QUIZ
    ensure_quest_read(
        n_szb,
        "用兵之道",
        min_seconds=14,
        reward=55,
        order_index=1,
        desc="沉思阅读至少 14 秒",
    )
    ensure_quest_visit(
        n_szb,
        "篇章已阅",
        target="lore",
        reward=15,
        order_index=2,
        desc="确认已阅读节点解读",
    )
    ensure_quest_quiz_select(
        n_szb,
        "不战而屈人之兵出自？",
        question="“不战而屈人之兵”出自：",
        mode="single",
        options=[
            {"id": "A", "text": "《孙子兵法》"},
            {"id": "B", "text": "《韩非子》"},
            {"id": "C", "text": "《战国策》"},
        ],
        answer=["A"],
        reward=20,
        order_index=3,
        desc="基础理解单选题",
    )

    # Lv3 天工开物：1x READ + 1x COLLECT
    ensure_quest_read(
        n_tgkw,
        "格物致用",
        min_seconds=18,
        reward=70,
        order_index=1,
        desc="专注阅读至少 18 秒，窥其造物本旨",
    )
    ensure_quest_collect(
        n_tgkw,
        "器用归类",
        count=3,
        reward=35,
        order_index=2,
        desc="整理归类三项器用知识点",
    )
    ensure_quest_visit(
        n_tgkw,
        "器物图鉴已览",
        target="atlas",
        reward=20,
        order_index=3,
        desc="浏览相关器物图鉴或资料",
    )

    # --- 非遗 Lv3：景泰蓝（READ + VISIT + QUIZ_MULTI）---
    ensure_quest_read(
        n_fy_jtl,
        "掐丝珐琅工艺",
        min_seconds=16,
        reward=60,
        order_index=1,
        desc="了解掐丝、点蓝、烧制、打磨、镀金等流程",
    )
    ensure_quest_visit(
        n_fy_jtl,
        "器物赏鉴",
        target="atlas",
        reward=20,
        order_index=2,
        desc="确认观看器物图鉴/展陈资料",
    )
    ensure_quest_quiz_select(
        n_fy_jtl,
        "景泰蓝流程辨识",
        question="以下哪些步骤属于景泰蓝制作流程？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "掐丝"},
            {"id": "B", "text": "点蓝"},
            {"id": "C", "text": "镂空雕漆"},
            {"id": "D", "text": "烧制与打磨"},
        ],
        answer=["A", "B", "D"],
        reward=25,
        order_index=3,
        desc="巩固对工艺流程的理解",
    )

    # 小游戏任务放入既有节点：
    ensure_quest_match(
        n_szb,
        "兵法要语配对",
        items=[
            {"left": "知彼知己", "right": "百战不殆"},
            {"left": "上兵伐谋", "right": "谋略为上"},
            {"left": "兵者", "right": "诡道也"},
        ],
        pairs=3,
        reward=20,
        order_index=4,
        desc="配对兵法术语与释义。",
        time_limit=120,
    )
    ensure_quest_order(
        n_fy_jtl,
        "工序排序 · 景泰蓝",
        sequence=["掐丝", "点蓝", "烧制", "打磨", "镀金"],
        initial=["打磨", "掐丝", "镀金", "点蓝", "烧制"],
        reward=30,
        order_index=4,
        desc="将景泰蓝制作工序按正确顺序排列。",
        time_limit=120,
    )
    # 额外：在《道德经》添加一组入门配对；在“宣纸制作”添加工序排序（高阶）
    ensure_quest_match(
        n_ddj,
        "道家观念配对",
        items=[
            {"left": "道法自然", "right": "顺其自然而无为"},
            {"left": "上善若水", "right": "善利万物而不争"},
            {"left": "反者道之动", "right": "对立互转、循环不息"},
        ],
        pairs=3,
        reward=25,
        order_index=4,
        desc="把名句与要义一一对应。",
        time_limit=120,
    )
    ensure_quest_order(
        n_fy_xz_lv5,
        "工序排序 · 宣纸",
        sequence=["选料", "捣浆", "抄纸", "晒纸"],
        initial=["捣浆", "晒纸", "选料", "抄纸"],
        reward=30,
        order_index=5,
        desc="将宣纸制作工序排列为正确顺序。",
        time_limit=150,
    )

    # 清理：如已存在“探索小游戏 · 练习场”，彻底移除（其任务与边关系一并清理）
    lab = (
        db.query(ExplorationNode)
        .filter(ExplorationNode.code == "mini_game_lab")
        .first()
    )
    if lab:
        # 删除与该节点相关的解锁边
        db.query(NodeEdge).filter(
            (NodeEdge.from_node_id == lab.id) | (NodeEdge.to_node_id == lab.id)
        ).delete(synchronize_session=False)
        # 找到其任务并删除关联的用户进度
        lab_qids = [q.id for q in db.query(Quest).filter(Quest.node_id == lab.id).all()]
        if lab_qids:
            db.query(UserQuest).filter(UserQuest.quest_id.in_(lab_qids)).delete(
                synchronize_session=False
            )
            db.query(Quest).filter(Quest.id.in_(lab_qids)).delete(
                synchronize_session=False
            )
        # 删除节点本体
        db.delete(lab)
        db.commit()

    # Lv3 庄子：1x READ + 1x VISIT + 1x QUIZ
    ensure_quest_read(
        n_zz,
        "逍遥游",
        min_seconds=18,
        reward=70,
        order_index=1,
        desc="静读至少 18 秒，体会逍遥之旨",
    )
    ensure_quest_visit(
        n_zz,
        "寓言已览",
        target="lore",
        reward=20,
        order_index=2,
        desc="确认已阅读寓言段落解读",
    )
    ensure_quest_quiz_select(
        n_zz,
        "齐物论的思想核心",
        question="以下哪一项更贴近庄子的思想脉络？",
        mode="single",
        options=[
            {"id": "A", "text": "齐物论"},
            {"id": "B", "text": "兼爱非攻"},
            {"id": "C", "text": "仁政礼治"},
        ],
        answer=["A"],
        reward=25,
        order_index=3,
        desc="基础理解单选题",
    )

    # 新增：为部分节点增加多选题（QUIZ_SELECT, mode='multi'）
    ensure_quest_quiz_select(
        n_shj,
        "《山海经》意象辨识",
        question="下列哪些意象更常见于《山海经》的叙述中？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "奇禽异兽"},
            {"id": "B", "text": "山川地理志"},
            {"id": "C", "text": "秦汉赋体"},
            {"id": "D", "text": "神话传说"},
        ],
        answer=["A", "B", "D"],
        reward=25,
        order_index=4,
        desc="多选题：强化对志怪/地理/神话元素的把握",
    )

    ensure_quest_quiz_select(
        n_tgkw,
        "《天工开物》的知识门类",
        question="以下哪些属于《天工开物》中讨论的门类？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "农桑"},
            {"id": "B", "text": "冶铸"},
            {"id": "C", "text": "兵阵"},
            {"id": "D", "text": "陶瓷"},
        ],
        answer=["A", "B", "D"],
        reward=30,
        order_index=4,
        desc="多选题：识别工艺门类",
    )

    # Lv4 周易：至少 1 个任务
    ensure_quest_read(
        n_zy,
        "卦象导读",
        min_seconds=22,
        reward=80,
        order_index=1,
        desc="耐心阅读至少 22 秒，初识变易之理",
    )
    ensure_quest_quiz_select(
        n_zy,
        "卦象与爻辞",
        question="关于《周易》的说法，哪些更准确？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "卦由阴阳爻组成"},
            {"id": "B", "text": "只有卦辞没有爻辞"},
            {"id": "C", "text": "强调变易之理"},
            {"id": "D", "text": "属于纯粹的占卜术，没有思想价值"},
        ],
        answer=["A", "C"],
        reward=30,
        order_index=2,
        desc="多选题：基础辨析",
    )

    # --- 非遗 Lv4：昆曲（READ + VISIT + COLLECT + QUIZ_SINGLE）---
    ensure_quest_read(
        n_fy_kq,
        "曲韵水磨",
        min_seconds=20,
        reward=70,
        order_index=1,
        desc="认识昆曲声腔、曲牌与表演程式",
    )
    ensure_quest_visit(
        n_fy_kq,
        "折子戏观演",
        target="lore",
        reward=20,
        order_index=2,
        desc="确认阅读 AI 卷轴/观看片段",
    )
    ensure_quest_collect(
        n_fy_kq,
        "身段名目",
        count=2,
        reward=25,
        order_index=3,
        desc="记录两种基本身段/水袖身法",
    )
    ensure_quest_quiz_select(
        n_fy_kq,
        "昆曲称号",
        question="昆曲常被誉为？",
        mode="single",
        options=[
            {"id": "A", "text": "百戏之祖"},
            {"id": "B", "text": "南戏之末"},
            {"id": "C", "text": "曲艺之源"},
        ],
        answer=["A"],
        reward=25,
        order_index=4,
        desc="基础常识小测",
    )

    # --- Lv5 典籍：左传（READ + VISIT + QUIZ_MULTI）---
    ensure_quest_read(
        n_zz_lv5,
        "春秋编年",
        min_seconds=24,
        reward=90,
        order_index=1,
        desc="细读春秋史事，理解编年体例",
    )
    ensure_quest_visit(
        n_zz_lv5,
        "史家笔法",
        target="lore",
        reward=20,
        order_index=2,
        desc="确认阅读 AI 卷轴或相关史料",
    )
    ensure_quest_quiz_select(
        n_zz_lv5,
        "《左传》特点",
        question="以下哪些是《左传》的特点？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "编年体"},
            {"id": "B", "text": "纪传体"},
            {"id": "C", "text": "记录春秋史事"},
            {"id": "D", "text": "以诗经注解为主"},
        ],
        answer=["A", "C"],
        reward=30,
        order_index=3,
        desc="史学体例辨识",
    )

    # --- Lv5 典籍：荀子（READ + COLLECT + QUIZ_SINGLE）---
    ensure_quest_read(
        n_xz_lv5,
        "性恶与礼治",
        min_seconds=24,
        reward=90,
        order_index=1,
        desc="深入理解荀子的性恶论与礼法思想",
    )
    ensure_quest_collect(
        n_xz_lv5,
        "核心论点",
        count=3,
        reward=35,
        order_index=2,
        desc="整理三个核心论点或名句",
    )
    ensure_quest_quiz_select(
        n_xz_lv5,
        "荀子主张",
        question="荀子的核心主张是？",
        mode="single",
        options=[
            {"id": "A", "text": "性本善"},
            {"id": "B", "text": "性本恶"},
            {"id": "C", "text": "无善无恶"},
        ],
        answer=["B"],
        reward=25,
        order_index=3,
        desc="基础辨析",
    )

    # --- 非遗 Lv5：宣纸制作（READ + VISIT + COLLECT + QUIZ_MULTI）---
    ensure_quest_read(
        n_fy_xz_lv5,
        "千年纸寿工艺",
        min_seconds=24,
        reward=85,
        order_index=1,
        desc="了解选料、捣浆、抄纸、晒纸等流程",
    )
    ensure_quest_visit(
        n_fy_xz_lv5,
        "纸作图鉴",
        target="atlas",
        reward=20,
        order_index=2,
        desc="观看工艺图鉴或实地考察记录",
    )
    ensure_quest_collect(
        n_fy_xz_lv5,
        "材料识别",
        count=3,
        reward=30,
        order_index=3,
        desc="记录三种原料或工具（青檀皮、稻草、竹帘等）",
    )
    ensure_quest_quiz_select(
        n_fy_xz_lv5,
        "宣纸关键工序",
        question="宣纸制作的关键工序包括？（多选）",
        mode="multi",
        options=[
            {"id": "A", "text": "选料"},
            {"id": "B", "text": "抄纸"},
            {"id": "C", "text": "烧窑"},
            {"id": "D", "text": "晒纸"},
        ],
        answer=["A", "B", "D"],
        reward=30,
        order_index=4,
        desc="工艺流程小测",
    )

    # --- 非遗 Lv5：古琴艺术（READ + VISIT + COLLECT + QUIZ_SINGLE）---
    ensure_quest_read(
        n_fy_gq_lv5,
        "琴者心也",
        min_seconds=24,
        reward=85,
        order_index=1,
        desc="认识古琴制式、指法与代表曲目",
    )
    ensure_quest_visit(
        n_fy_gq_lv5,
        "琴音聆赏",
        target="lore",
        reward=20,
        order_index=2,
        desc="确认阅读 AI 卷轴或聆听示范曲",
    )
    ensure_quest_collect(
        n_fy_gq_lv5,
        "曲目清单",
        count=2,
        reward=25,
        order_index=3,
        desc="记录两首代表曲目（流水、广陵散等）",
    )
    ensure_quest_quiz_select(
        n_fy_gq_lv5,
        "古琴特点",
        question="古琴通常有几根弦？",
        mode="single",
        options=[
            {"id": "A", "text": "五弦"},
            {"id": "B", "text": "七弦"},
            {"id": "C", "text": "九弦"},
        ],
        answer=["B"],
        reward=25,
        order_index=4,
        desc="基础常识",
    )

    # --- Unlock Edges ---
    # Lv1 -> Lv2
    ensure_edge("root_daodejing", "root_shanhaijing")
    ensure_edge("root_shijing", "root_sunzibingfa")
    # Lv2 -> Lv3
    ensure_edge("root_shijing", "root_tiangongkaiwu")
    ensure_edge("root_shanhaijing", "root_tiangongkaiwu")
    ensure_edge("root_sunzibingfa", "root_tiangongkaiwu")
    ensure_edge("root_shanhaijing", "root_zhuangzi")
    ensure_edge("root_sunzibingfa", "root_zhuangzi")
    # Lv3 -> Lv4
    ensure_edge("root_tiangongkaiwu", "root_zhouyi")
    ensure_edge("root_zhuangzi", "root_zhouyi")

    # 非遗专题解锁链：Lv1 皮影戏 -> Lv2 木版年画 -> Lv3 景泰蓝 -> Lv4 昆曲
    ensure_edge("fy_piyingxi", "fy_mubannianhua")
    ensure_edge("fy_mubannianhua", "fy_jingtailan")
    ensure_edge("fy_jingtailan", "fy_kunqu")

    # 清理：对于每个节点，若存在多个 READ，仅保留 order_index 最小者，其余删除（同时删除关联的 UserQuest）
    for n in db.query(ExplorationNode).all():
        reads = (
            db.query(Quest)
            .filter(Quest.node_id == n.id, Quest.type == "READ")
            .order_by(Quest.order_index.asc(), Quest.id.asc())
            .all()
        )
        if len(reads) > 1:
            keep = reads[0]
            remove_ids = [q.id for q in reads[1:]]
            if remove_ids:
                db.query(UserQuest).filter(UserQuest.quest_id.in_(remove_ids)).delete(
                    synchronize_session=False
                )
                db.query(Quest).filter(Quest.id.in_(remove_ids)).delete(
                    synchronize_session=False
                )
                db.commit()
                removed_legacy_reads += len(remove_ids)

    # --- Achievements (ensure minimal set) ---
    ensure_achievement(
        "QUESTS_DONE_1",
        "初试身手",
        "完成 1 个任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 1},
        5,
    )
    ensure_achievement(
        "EXP_50",
        "初获经验",
        "累计经验达到 50",
        {"type": "TOTAL_EXP_AT_LEAST", "value": 50},
        3,
    )
    # New: READ/VISIT/COLLECT 各类型入门与进阶
    ensure_achievement(
        "READ_STARTER",
        "读书入门",
        "完成 1 个 READ 任务",
        {
            "type": "QUESTS_DONE_AT_LEAST",
            "value": 1,
        },  # 简化：当前统计未分类型，这里沿用通用条件
        2,
    )
    ensure_achievement(
        "VISIT_STARTER",
        "足迹初留",
        "完成 1 个 VISIT 任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 2},  # 与上叠加形成阶梯激励
        2,
    )
    ensure_achievement(
        "COLLECT_STARTER",
        "采撷之始",
        "完成 1 个 COLLECT 任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 3},
        3,
    )
    # New: 等级达成
    ensure_achievement(
        "LEVEL_2",
        "学有所成 · Lv.2",
        "达到等级 2",
        {"type": "LEVEL_REACHED", "value": 2},
        4,
    )
    ensure_achievement(
        "LEVEL_3",
        "精进不怠 · Lv.3",
        "达到等级 3",
        {"type": "LEVEL_REACHED", "value": 3},
        6,
    )

    # --- 非遗专题：分类类 Achievements ---
    ensure_achievement(
        "ICH_STARTER",
        "非遗入门",
        "完成任一非遗节点的 1 个任务",
        {"type": "QUESTS_DONE_IN_CATEGORY_AT_LEAST", "category": "非遗", "value": 1},
        4,
    )
    ensure_achievement(
        "ICH_PRACTITIONER",
        "非遗实践者",
        "在非遗节点累计完成 5 个任务",
        {"type": "QUESTS_DONE_IN_CATEGORY_AT_LEAST", "category": "非遗", "value": 5},
        8,
    )
    ensure_achievement(
        "ICH_EXPLORER",
        "非遗探索者",
        "触达 3 个不同的非遗节点（完成该节点任意任务）",
        {"type": "NODES_TOUCHED_IN_CATEGORY_AT_LEAST", "category": "非遗", "value": 3},
        10,
    )

    # 阶梯：任务总量
    ensure_achievement(
        "QUESTS_DONE_5",
        "渐入佳境",
        "完成 5 个任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 5},
        6,
    )
    ensure_achievement(
        "QUESTS_DONE_10",
        "持之以恒",
        "完成 10 个任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 10},
        10,
    )
    ensure_achievement(
        "QUESTS_DONE_20",
        "不舍昼夜",
        "完成 20 个任务",
        {"type": "QUESTS_DONE_AT_LEAST", "value": 20},
        16,
    )

    # 阶梯：经验 EXP
    ensure_achievement(
        "EXP_100",
        "学有所获",
        "累计经验达到 100",
        {"type": "TOTAL_EXP_AT_LEAST", "value": 100},
        6,
    )
    ensure_achievement(
        "EXP_300",
        "厚积薄发",
        "累计经验达到 300",
        {"type": "TOTAL_EXP_AT_LEAST", "value": 300},
        12,
    )
    ensure_achievement(
        "EXP_600",
        "学海无涯",
        "累计经验达到 600",
        {"type": "TOTAL_EXP_AT_LEAST", "value": 600},
        18,
    )

    # 阶梯：等级 Level
    ensure_achievement(
        "LEVEL_4",
        "学有专精 · Lv.4",
        "达到等级 4",
        {"type": "LEVEL_REACHED", "value": 4},
        10,
    )
    ensure_achievement(
        "LEVEL_5",
        "融会贯通 · Lv.5",
        "达到等级 5",
        {"type": "LEVEL_REACHED", "value": 5},
        14,
    )

    # 任务类型分项（READ/VISIT/COLLECT/QUIZ_SELECT）
    ensure_achievement(
        "READ_3",
        "读有所成",
        "完成 3 个 READ 任务",
        {"type": "QUESTS_DONE_TYPE_AT_LEAST", "quest_type": "READ", "value": 3},
        5,
    )
    ensure_achievement(
        "READ_8",
        "博览群章",
        "完成 8 个 READ 任务",
        {"type": "QUESTS_DONE_TYPE_AT_LEAST", "quest_type": "READ", "value": 8},
        12,
    )
    ensure_achievement(
        "VISIT_3",
        "行有迹",
        "完成 3 个 VISIT 任务",
        {"type": "QUESTS_DONE_TYPE_AT_LEAST", "quest_type": "VISIT", "value": 3},
        4,
    )
    ensure_achievement(
        "COLLECT_3",
        "集腋成裘",
        "完成 3 个 COLLECT 任务",
        {"type": "QUESTS_DONE_TYPE_AT_LEAST", "quest_type": "COLLECT", "value": 3},
        6,
    )
    ensure_achievement(
        "QUIZ_3",
        "问道有成",
        "完成 3 个 QUIZ 任务",
        {"type": "QUESTS_DONE_TYPE_AT_LEAST", "quest_type": "QUIZ_SELECT", "value": 3},
        8,
    )

    # 触及节点数量（至少完成每个节点的一项任务）
    ensure_achievement(
        "NODES_3",
        "三足之势",
        "在 3 个不同节点完成任务",
        {"type": "NODES_TOUCHED_AT_LEAST", "value": 3},
        6,
    )
    ensure_achievement(
        "NODES_5",
        "五路并进",
        "在 5 个不同节点完成任务",
        {"type": "NODES_TOUCHED_AT_LEAST", "value": 5},
        10,
    )

    return {
        "nodes": created_nodes,
        "quests": created_quests,
        "achievements": created_achs,
        "removed_legacy_reads": removed_legacy_reads,
    }
